如何测定 TCP 传输的延迟?

您所在的位置:网站首页 unity udp通信延迟 如何测定 TCP 传输的延迟?

如何测定 TCP 传输的延迟?

2024-06-07 12:42| 来源: 网络整理| 查看: 265

我如果想要知道一个字节从一个终端经由TCP传输到另一个终端所耗费的时间,即 TCP 的传输延迟(Latency),应该怎么办?

经过查阅相关信息,发现了两种方案:

TCPing(绝大部分答案,但真的如此吗?) 通过手动构造 TCP SYN 包来测量延迟(github.com/grahamking/latency)

然后还有我的灵光一现…

关于 TCPing

尝试搜索了一下 TCPing 的原理,却发现没有一个人从传输层的角度对此进行说明,我甚至得不到我想要的答案。

于是在 GitHub 上搜了一下关键字 tcping,star 最多的项目是 github.com/cloverstd/tcping,很巧是基于 Go 语言实现的,立刻 clone 下来查看源码,却发现乏善可陈…

cloverstd/tcping入口123456789101112131415161718192021// 省略了大部分代码var rootCmd = cobra.Command{ Run: func(cmd *cobra.Command, args []string) { // 拿到 host,即命令行参数,可以为域名,如 google.com host := args[0] // 域名不会被 Format,这个方法的实现有 BUG parseHost := ping.FormatIP(host) // 构造了 target target := ping.Target{ Host: parseHost, } // 判断协议 switch protocol { case ping.TCP: pinger = ping.NewTCPing() } pinger.SetTarget(&target) pingerDone := pinger.Start() // 从这里进去 ping },} 123456789// Start,省略了大部分代码func (tcping TCPing) Start() 建立连接并马上关闭连接 –> 计时结束

zhengxiaowai/tcping

前面一个比较离谱,再看一个吧:zhengxiaowai/tcping,Python 实现的 TCPing,star 数第二名

入口12if __name__ == '__main__': cli()

main 函数进来直接到 cli

[email protected]()@click.argument('host')def cli(host, port, count, timeout, report): ping = Ping(host, port, timeout) # 直接拿命令行参数实例化类 try: ping.ping(count) # 进入 ping TCPing 实现

可以看到就是建立连接到关闭连接,调用了 connect

123456# TCPing,省略了绝大部分代码def ping(self, count=10): cost_time = self.timer.cost( (s.connect, s.shutdown), ((self._host, self._port), None)) self.statistics(n) connect

再看看这个 connect,唔姆,如出一辙,直接传域名

12def connect(self, host, port=80): self._s.connect((host, int(port))) i3h/tcping

再看一个比较靠谱的,i3h/tcping,同样是 Go 实现,入口部分就不再贴了。这位老哥其实没有实现 TCPing,实现的是 HTTPing

12345678910111213141516171819202122232425262728293031323334353637383940414243444546// 其中一部分代码,可以看到大概分成了DNS LookUp,TCP 握手和 TLS 握手trace := &httptrace.ClientTrace{ DNSStart: func(info httptrace.DNSStartInfo) { t0 = time.Now().UnixNano() }, DNSDone: func(info httptrace.DNSDoneInfo) { t1 = time.Now().UnixNano() if info.Err != nil { err = info.Err log.Fatal(info.Err) } }, ConnectStart: func(net, addr string) { }, ConnectDone: func(net, addr string, err error) { if err != nil { log.Fatalf("unable to connect to host %v: %v", addr, err) } t2 = time.Now().UnixNano() }, GotConn: func(info httptrace.GotConnInfo) { t3 = time.Now().UnixNano() }, GotFirstResponseByte: func() { t4 = time.Now().UnixNano() }, TLSHandshakeStart: func() { t5 = time.Now().UnixNano() }, TLSHandshakeDone: func(_ tls.ConnectionState, _ error) { t6 = time.Now().UnixNano() },}// 后面分别计算了不同阶段的耗时if t0 == 0 { t0 = t2 t1 = t2}stats.DNS = t1 - t0stats.TCP = t2 - t1stats.Process = t4 - t3stats.Transfer = t7 - t4stats.TLS = t6 - t5stats.Total = t7 - t0

以上便是 TCPing 的实现情况,然而 TCP 的握手再关闭应该是多个 RTT 吧?

按照标准的三次握手四次挥手来算,连接然后立刻关闭应该是 3 个 RTT,但是也不能简单的除 3,因为有时候挥手的 FIN & ACK 是合并发送的,或者有更加复杂的情况…

TCPing 的过程包含多个 RTT,并不是我想知道的“从一端到另一端的延迟”。

那么除了 TCPing,是否有更科学的方法?

手动构造 TCP SYN 包

GitHub 搜索 TCP Latency,我们可以发现这个尘封的 Go Project(上一次 Commit 是 2016 年)

grahamking/latency

latency sends a TCP SYN packet (the opening of the three-way handshake) to a remote host on port 80. That host will respond with either a RST (if the port is closed), or a SYN/ACK (if the port is open). Either way, we time how long it takes between sending the SYN and receiving the response. That’s your network latency.

根据 README,我们手动构造一个 SYN 包发送出去,然后监听收到的SYN & ACK,即TCP三次握手中的前两次,这样便是实实在在的一个 RTT,跟描述一样,这个项目真的手撸了一个 TCP 打包…

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121const ( FIN = 1 // 00 0001 SYN = 2 // 00 0010 RST = 4 // 00 0100 PSH = 8 // 00 1000 ACK = 16 // 01 0000 URG = 32 // 10 0000)type TCPHeader struct { Source uint16 Destination uint16 SeqNum uint32 AckNum uint32 DataOffset uint8 // 4 bits Reserved uint8 // 3 bits ECN uint8 // 3 bits Ctrl uint8 // 6 bits Window uint16 Checksum uint16 // Kernel will set this if it's 0 Urgent uint16 Options []TCPOption}// 手动打包头func NewTCPHeader(data []byte) *TCPHeader { var tcp TCPHeader r := bytes.NewReader(data) binary.Read(r, binary.BigEndian, &tcp.Source) binary.Read(r, binary.BigEndian, &tcp.Destination) binary.Read(r, binary.BigEndian, &tcp.SeqNum) binary.Read(r, binary.BigEndian, &tcp.AckNum) var mix uint16 binary.Read(r, binary.BigEndian, &mix) tcp.DataOffset = byte(mix >> 12) // top 4 bits tcp.Reserved = byte(mix >> 9 & 7) // 3 bits tcp.ECN = byte(mix >> 6 & 7) // 3 bits tcp.Ctrl = byte(mix & 0x3f) // bottom 6 bits binary.Read(r, binary.BigEndian, &tcp.Window) binary.Read(r, binary.BigEndian, &tcp.Checksum) binary.Read(r, binary.BigEndian, &tcp.Urgent) return &tcp}// 手动 TCP Checksumfunc Csum(data []byte, srcip, dstip [4]byte) uint16 { pseudoHeader := []byte{ srcip[0], srcip[1], srcip[2], srcip[3], dstip[0], dstip[1], dstip[2], dstip[3], 0, // zero 6, // protocol number (6 == TCP) 0, byte(len(data)), // TCP length (16 bits), not inc pseudo header } sumThis := make([]byte, 0, len(pseudoHeader)+len(data)) sumThis = append(sumThis, pseudoHeader...) sumThis = append(sumThis, data...) //fmt.Printf("% x\n", sumThis) lenSumThis := len(sumThis) var nextWord uint16 var sum uint32 for i := 0; i+1 < lenSumThis; i += 2 { nextWord = uint16(sumThis[i]) 16) + (sum & 0xffff) sum = sum + (sum >> 16) // Bitwise complement return uint16(^sum)}// 手动解析 TCP 包头func (tcp *TCPHeader) Marshal() []byte { buf := new(bytes.Buffer) binary.Write(buf, binary.BigEndian, tcp.Source) binary.Write(buf, binary.BigEndian, tcp.Destination) binary.Write(buf, binary.BigEndian, tcp.SeqNum) binary.Write(buf, binary.BigEndian, tcp.AckNum) var mix uint16 mix = uint16(tcp.DataOffset)


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3